package com.managertrade.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.managertrade.dao.sqlite.entity.KlineWithIndicatorsEntity;
import com.managertrade.dto.BacktestRequestDTO;
import com.managertrade.dto.vo.BacktestResultVO;
import com.managertrade.dto.vo.KlineWithIndicators;
import com.managertrade.service.sqllite.service.KlineWithIndicatorsService;
import com.managertrade.util.TechnicalIndicatorUtils;
import org.apache.commons.compress.utils.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;

@Service
public class BacktestService {

    @Autowired
    private KlineWithIndicatorsService klineWithIndicatorsService;

    /**
     * 回测入口
     */
    public BacktestResultVO runBacktest(BacktestRequestDTO request) {
        // 1️⃣ 获取基础K线数据（分页批量读取）
        List<KlineWithIndicators> klines = loadHistoricalData(request.getCoin(), request.getTimeframe());
        int size = klines.size();
        if (size == 0) return emptyResult(request);

        // 2️⃣ 初始化指标缓存
        Map<String, List<Double>> emaCache = new HashMap<>();
        Map<String, double[][]> macdCache = new HashMap<>();
        Map<String, List<Double>> bollCache = new HashMap<>();
        List<Double> volMaCache = new ArrayList<>();
        List<Double> rsiCache = new ArrayList<>();
        double[] kCache = new double[size];
        double[] dCache = new double[size];
        List<Double> atrCache = new ArrayList<>();
        List<Double> cciCache = new ArrayList<>();
        List<Double> obvCache = new ArrayList<>();

        emaCache.put("EMA.7", new ArrayList<>());
        emaCache.put("EMA.21", new ArrayList<>());
        bollCache.put("upper", new ArrayList<>());
        bollCache.put("middle", new ArrayList<>());
        bollCache.put("lower", new ArrayList<>());
        macdCache.put("MACD", new double[size][]);

        // 3️⃣ 遍历K线计算所有指标
        for (int i = 0; i < size; i++) {
            KlineWithIndicators k = klines.get(i);

            // EMA
            TechnicalIndicatorUtils.calcEMA(klines, 7, i, emaCache.get("EMA.7"));
            TechnicalIndicatorUtils.calcEMA(klines, 21, i, emaCache.get("EMA.21"));

            // MACD
            double[] macd = TechnicalIndicatorUtils.calcMACD(klines, i,
                    emaCache.get("EMA.7"), emaCache.get("EMA.21"), new double[size]);
            macdCache.get("MACD")[i] = macd;

            // BOLL
            double[] boll = TechnicalIndicatorUtils.calcBOLL(klines, 20, 2.0, i, bollCache.get("middle"));
            bollCache.get("upper").add(boll[0]);
            bollCache.get("middle").add(boll[1]);
            bollCache.get("lower").add(boll[2]);

            // 成交量
            TechnicalIndicatorUtils.calcVolumeMA(klines, 20, i, volMaCache);

            // RSI
            TechnicalIndicatorUtils.calcRSI(klines, 14, i, rsiCache);

            // KDJ
            TechnicalIndicatorUtils.calcKDJ(klines, i, 9, 3, 3, kCache, dCache);

            // ATR
            TechnicalIndicatorUtils.calcATR(klines, 14, i, atrCache);

            // CCI
            TechnicalIndicatorUtils.calcCCI(klines, 14, i, cciCache);

            // OBV
            TechnicalIndicatorUtils.calcOBV(klines, i, obvCache);
        }

        // 4️⃣ 模拟交易
        BigDecimal balance = request.getAmount();
        BigDecimal position = BigDecimal.ZERO;

        // 4️⃣ 模拟交易（考虑手续费和滑点）
        BigDecimal feeRate = request.getFeeRate() != null ? request.getFeeRate() : BigDecimal.ZERO;
        BigDecimal slippageRate = request.getSlippageRate() != null ? request.getSlippageRate() : BigDecimal.ZERO;

        for (int i = 0; i < size; i++) {
            KlineWithIndicators k = klines.get(i);
            boolean buySignal = true;
            boolean sellSignal = true;

            for (BacktestRequestDTO.IndicatorDTO ind : request.getIndicators()) {
                boolean cond = evaluateCondition(k, i, ind.getCondition(),
                        emaCache, macdCache, bollCache, volMaCache,
                        rsiCache, kCache, dCache, atrCache, cciCache, obvCache);
                buySignal &= cond;
                sellSignal &= !cond;
            }

            BigDecimal price = BigDecimal.valueOf(k.getClose());

            if (buySignal && balance.compareTo(BigDecimal.ZERO) > 0) {
                // 买入价格考虑滑点
                BigDecimal tradePrice = price.multiply(BigDecimal.ONE.add(slippageRate));
                BigDecimal costFee = balance.multiply(feeRate);
                position = balance.subtract(costFee).divide(tradePrice, 8, BigDecimal.ROUND_DOWN);
                balance = BigDecimal.ZERO;
            } else if (sellSignal && position.compareTo(BigDecimal.ZERO) > 0) {
                // 卖出价格考虑滑点
                BigDecimal tradePrice = price.multiply(BigDecimal.ONE.subtract(slippageRate));
                BigDecimal sellAmount = position.multiply(tradePrice);
                BigDecimal costFee = sellAmount.multiply(feeRate);
                balance = sellAmount.subtract(costFee);
                position = BigDecimal.ZERO;
            }
        }

        // 5️⃣ 最终结算
        if (position.compareTo(BigDecimal.ZERO) > 0) {
            balance = position.multiply(BigDecimal.valueOf(klines.get(size - 1).getClose()));
        }

        // 6️⃣ 返回结果
        BacktestResultVO result = new BacktestResultVO();
        result.setCoin(request.getCoin());
        result.setTimeframe(request.getTimeframe());
        result.setInitialAmount(request.getAmount());
        result.setFinalAmount(balance);
        result.setProfit(balance.subtract(request.getAmount()));
        result.setName(result.getName());
        return result;
    }

    // =================== 工具方法 ===================

    private List<KlineWithIndicators> loadHistoricalData(String coin, String timeframe) {
        List<KlineWithIndicators> klines = Lists.newArrayList();
        int page = 0;
        int batchSize = 500;
        while (true) {
            Pageable pageable = PageRequest.of(page, batchSize);
            List<KlineWithIndicatorsEntity> pageData = klineWithIndicatorsService.findBySymbolAndPeriodPaged(coin, timeframe, pageable);
            if (pageData == null || pageData.isEmpty()) break;
            klines.addAll(BeanUtil.copyToList(pageData, KlineWithIndicators.class));
            page++;
        }
        return klines;
    }

    private boolean evaluateCondition(KlineWithIndicators k, int index, BacktestRequestDTO.ConditionDTO condition,
                                      Map<String, List<Double>> emaCache,
                                      Map<String, double[][]> macdCache,
                                      Map<String, List<Double>> bollCache,
                                      List<Double> volMaCache,
                                      List<Double> rsiCache,
                                      double[] kCache,
                                      double[] dCache,
                                      List<Double> atrCache,
                                      List<Double> cciCache,
                                      List<Double> obvCache) {
        if (condition == null) return true;
        BigDecimal left = extractValue(k, index, condition.getLeft(),
                emaCache, macdCache, bollCache, volMaCache, rsiCache, kCache, dCache, atrCache, cciCache, obvCache);
        BigDecimal right = extractValueOrNumber(k, index, condition.getRight(),
                emaCache, macdCache, bollCache, volMaCache, rsiCache, kCache, dCache, atrCache, cciCache, obvCache);

        switch (condition.getOperator()) {
            case ">": return left.compareTo(right) > 0;
            case "<": return left.compareTo(right) < 0;
            case "=": return left.compareTo(right) == 0;
            case ">=": return left.compareTo(right) >= 0;
            case "<=": return left.compareTo(right) <= 0;
            default: return false;
        }
    }

    private BigDecimal extractValue(KlineWithIndicators k, int index, String field,
                                    Map<String, List<Double>> emaCache,
                                    Map<String, double[][]> macdCache,
                                    Map<String, List<Double>> bollCache,
                                    List<Double> volMaCache,
                                    List<Double> rsiCache,
                                    double[] kCache,
                                    double[] dCache,
                                    List<Double> atrCache,
                                    List<Double> cciCache,
                                    List<Double> obvCache) {
        switch (field) {
            case "EMA.short": return BigDecimal.valueOf(emaCache.get("EMA.7").get(index));
            case "EMA.long": return BigDecimal.valueOf(emaCache.get("EMA.21").get(index));


            case "MACD": return BigDecimal.valueOf(macdCache.get("MACD")[index][0]);
            case "MACD.dea": return BigDecimal.valueOf(macdCache.get("MACD")[index][1]);
            case "MACD.bar": return BigDecimal.valueOf(macdCache.get("MACD")[index][2]);

            case "BOLL.upper": return BigDecimal.valueOf(bollCache.get("upper").get(index));
            case "BOLL.middle": return BigDecimal.valueOf(bollCache.get("middle").get(index));
            case "BOLL.lower": return BigDecimal.valueOf(bollCache.get("lower").get(index));

            case "Volume": return BigDecimal.valueOf(volMaCache.get(index));

            case "RSI": return BigDecimal.valueOf(rsiCache.get(index));

            case "KDJ.k": return BigDecimal.valueOf(kCache[index]);
            case "KDJ.d": return BigDecimal.valueOf(dCache[index]);
            case "KDJ.j": return BigDecimal.valueOf(3 * kCache[index] - 2 * dCache[index]);

            case "ATR": return BigDecimal.valueOf(atrCache.get(index));
            case "CCI": return BigDecimal.valueOf(cciCache.get(index));
            case "OBV": return BigDecimal.valueOf(obvCache.get(index));

            default: return BigDecimal.ZERO;
        }
    }

    private BigDecimal extractValueOrNumber(KlineWithIndicators k, int index, String value,
                                            Map<String, List<Double>> emaCache,
                                            Map<String, double[][]> macdCache,
                                            Map<String, List<Double>> bollCache,
                                            List<Double> volMaCache,
                                            List<Double> rsiCache,
                                            double[] kCache,
                                            double[] dCache,
                                            List<Double> atrCache,
                                            List<Double> cciCache,
                                            List<Double> obvCache) {
        try {
            return new BigDecimal(value);
        } catch (NumberFormatException e) {
            return extractValue(k, index, value, emaCache, macdCache, bollCache, volMaCache,
                    rsiCache, kCache, dCache, atrCache, cciCache, obvCache);
        }
    }

    private BacktestResultVO emptyResult(BacktestRequestDTO request) {
        BacktestResultVO result = new BacktestResultVO();
        result.setCoin(request.getCoin());
        result.setTimeframe(request.getTimeframe());
        result.setInitialAmount(request.getAmount());
        result.setFinalAmount(request.getAmount());
        result.setProfit(BigDecimal.ZERO);
        return result;
    }
}
